﻿using dnAnalytics.LinearAlgebra;
using dnAnalytics.Math;
using dnAnalytics.Tests.LinearAlgebra;
using NUnit.Framework;

namespace dnAnalytics.Tests
{
    public abstract class ApplyMatrixTests
    {
        protected abstract Matrix GetMatrix(double[,] data);

        [Test]
        public void CanApplyPowToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Pow(3.0);
            Assert.That(data[0, 0], Is.EqualTo(1));
            Assert.That(data[0, 1], Is.EqualTo(8));
            Assert.That(data[1, 0], Is.EqualTo(27));
            Assert.That(data[1, 1], Is.EqualTo(64));
        }

        [Test]
        public void CanApplyMapToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(x => 1 / x);
            Assert.That(data[0, 0], Is.EqualTo(1));
            Assert.That(data[0, 1], Is.EqualTo(.5));
            Assert.That(data[1, 0], Is.EqualTo(1.0 / 3.0));
            Assert.That(data[1, 1], Is.EqualTo(.25));
        }

        [Test]
        public void CanApplySqrtMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Sqrt);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Sqrt(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Sqrt(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Sqrt(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Sqrt(4.0)));
        }

       /* [Test]
        public void CanApplyInvertMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Invert);
            Assert.That(data[0, 0], Is.EqualTo(1));
            Assert.That(data[0, 1], Is.EqualTo(.5));
            Assert.That(data[1, 0], Is.EqualTo(1.0 / 3.0));
            Assert.That(data[1, 1], Is.EqualTo(.25));
        }*/

        [Test]
        public void CanApplyAbsMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { -1.0, -2.0 }, { -3.0, -4.0 } });
            data.Map(System.Math.Abs);
            Assert.That(data[0, 0], Is.EqualTo(1.0));
            Assert.That(data[0, 1], Is.EqualTo(2.0));
            Assert.That(data[1, 0], Is.EqualTo(3.0));
            Assert.That(data[1, 1], Is.EqualTo(4.0));
        }

        [Test]
        public void CanApplyExpMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Exp);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Exp(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Exp(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Exp(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Exp(4.0)));
        }

        [Test]
        public void CanApplyLog10MapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Log10);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Log10(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Log10(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Log10(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Log10(4.0)));
        }

        [Test]
        public void CanApplyLnMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Log);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Log(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Log(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Log(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Log(4.0)));
        }

        [Test]
        public void CanApplySinMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Sin);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Sin(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Sin(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Sin(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Sin(4.0)));
        }

        [Test]
        public void CanApplyCosMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Cos);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Cos(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Cos(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Cos(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Cos(4.0)));
        }

        [Test]
        public void CanApplyTanMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 3.0 } });
            data.Map(System.Math.Tan);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Tan(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Tan(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Tan(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Tan(3.0)));
        }

        [Test]
        public void CanApplyAsinMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Asin);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Asin(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Asin(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Asin(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Asin(4.0)));
        }

        [Test]
        public void CanApplyAcosMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Acos);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Acos(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Acos(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Acos(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Acos(4.0)));
        }

        [Test]
        public void CanApplyATanMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Atan);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Atan(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Atan(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Atan(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Atan(4.0)));
        }

        [Test]
        public void CanApplySinhMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Sinh);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Sinh(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Sinh(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Sinh(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Sinh(4.0)));
        }

        [Test]
        public void CanApplyCoshMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Cosh);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Cosh(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Cosh(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Cosh(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Cosh(4.0)));
        }

        [Test]
        public void CanApplyTanhMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(System.Math.Tanh);
            Assert.That(data[0, 0], Is.EqualTo(System.Math.Tanh(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(System.Math.Tanh(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(System.Math.Tanh(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(System.Math.Tanh(4.0)));
        }

        [Test]
        public void CanApplyErfMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(SpecialFunctions.Erf);
            Assert.That(data[0, 0], Is.EqualTo(SpecialFunctions.Erf(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(SpecialFunctions.Erf(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(SpecialFunctions.Erf(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(SpecialFunctions.Erf(4.0)));
        }

        [Test]
        public void CanApplyErfcMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(SpecialFunctions.Erfc);
            Assert.That(data[0, 0], Is.EqualTo(SpecialFunctions.Erfc(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(SpecialFunctions.Erfc(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(SpecialFunctions.Erfc(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(SpecialFunctions.Erfc(4.0)));
        }

        [Test]
        public void CanApplyErfInvMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { 1.0, 2.0 }, { 3.0, 4.0 } });
            data.Map(SpecialFunctions.ErfInv);
            Assert.That(data[0, 0], Is.EqualTo(SpecialFunctions.ErfInv(1.0)));
            Assert.That(data[0, 1], Is.EqualTo(SpecialFunctions.ErfInv(2.0)));
            Assert.That(data[1, 0], Is.EqualTo(SpecialFunctions.ErfInv(3.0)));
            Assert.That(data[1, 1], Is.EqualTo(SpecialFunctions.ErfInv(4.0)));
        }

        [Test]
        public void CanApplyErfcInvMapFunctionToMatrix()
        {
            var data = GetMatrix(new[,] { { .1, .2 }, { .3, .4 } });
            data.Map(SpecialFunctions.ErfcInv);
            Assert.That(data[0, 0], Is.EqualTo(SpecialFunctions.ErfcInv(.1)).Within(10).Ulps);
            Assert.That(data[0, 1], Is.EqualTo(SpecialFunctions.ErfcInv(.2)).Within(10).Ulps);
            Assert.That(data[1, 0], Is.EqualTo(SpecialFunctions.ErfcInv(.3)).Within(10).Ulps);
            Assert.That(data[1, 1], Is.EqualTo(SpecialFunctions.ErfcInv(.4)).Within(10).Ulps);
        }
    }

    [TestFixture]
    public class ApplyDenseMatrixTests : ApplyMatrixTests
    {
        protected override Matrix GetMatrix(double[,] data)
        {
            return new DenseMatrix(data);
        }
    }

    [TestFixture]
    public class ApplySparseMatrixTests : ApplyMatrixTests
    {
        protected override Matrix GetMatrix(double[,] data)
        {
            return new SparseMatrix(data);
        }
    }

    [TestFixture]
    public class ApplyBaseMatrixTests : ApplyMatrixTests
    {
        protected override Matrix GetMatrix(double[,] data)
        {
            return new BaseMatrix(data);
        }
    }

}